package cn.com.zero.base.util;

import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.query.SortQueryBuilder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author xiongxj
 * @version 1.0.0
 * @Description 操作redis的工具类，降低其他同事学习redis的成本
 * @createTime 2023/3/3 11:28
 */
public class RedisUtil<V> {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 连接操作redis的redisTemplate对象
     */
    private RedisTemplate<String, V> redisTemplate;


    //==================================key 相关操作 begin========================================

    /**
     * 设置 key 失效时间，指定默认时间单位为秒
     *
     * @param key  键
     * @param time 时间(秒)
     * @return true：设置失效时间成功；false：设置失效时间失败
     */
    public Boolean expire(String key, long time) {
        return expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 设置 key 失效时间，不指定默认时间单位
     *
     * @param key  键
     * @param time 时间
     * @param unit 时间单位
     * @return true：设置失效时间成功；false：设置失效时间失败
     */
    public Boolean expire(String key, long time, TimeUnit unit) {
        Assert.notNull(key, "key不允许为null");
        Assert.notNull(unit, "unit不允许为null");
        Boolean result = redisTemplate.expire(key, time, unit);
        return result != null && result;
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键
     * @return 时间(秒) 返回0代表为永久有效
     */
    public Long getExpire(String key) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 模糊查询获取key值
     *
     * @param pattern key的表达式
     * @return 满足条件的所有key键
     */
    public Set<String> keys(String pattern) {
        Assert.notNull(pattern, "pattern不允许为null");
        return redisTemplate.keys(pattern);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        Assert.notNull(key, "key不允许为null");
        Boolean result = redisTemplate.hasKey(key);
        return result != null && result;
    }

    /**
     * 删除缓存
     *
     * @param keys 可以传一个key键，也可以传多个key键
     * @return 删除成功的key键的个数
     */
    public Long delete(String... keys) {
        if (null == keys) {
            return 0L;
        }
        for (String key : keys) {
            Assert.notNull(key, "key不允许为null");
        }
        if (keys.length == 1) {
            Boolean result = redisTemplate.delete(keys[0]);
            if (result != null && result) {
                return 1L;
            }
            return 0L;
        } else {
            List<String> keyList = Arrays.asList(keys);
            return redisTemplate.delete(keyList);
        }
    }

    /**
     * 删除缓存
     *
     * @param keySet 待删除的键 set列表
     * @return 删除成功的key键的个数
     */
    public Long delete(Set<String> keySet) {
        if (CollectionUtils.isEmpty(keySet)) {
            return 0L;
        }
        for (String key : keySet) {
            Assert.notNull(key, "key不允许为null");
        }
        return redisTemplate.delete(keySet);
    }

    /**
     * 排序后分页，只针对list、set、zset排序 TODO 未测试
     * 先排序后分页，表示从第offset个元素开始，取出count个元素
     *
     * @param key             键
     * @param sortedByPattern 根据此pattern的value排序，除了常规的pattern，也可以接收hash的pattern
     * @param orderType       排序方式，升序、降序
     * @param offset          偏移量
     * @param count           每次查询的条数
     */
    public List<V> sort(String key, String sortedByPattern, SortParameters.Order orderType, Long offset, Long count) {
        return redisTemplate.sort(SortQueryBuilder
                .sort(key)
                .by(sortedByPattern)
                .alphabetical(true)
                .order(orderType)
                .limit(offset, count)
                .build());
    }

    //==================================key 相关操作   end========================================

    //==================================普通对象 相关操作 begin========================================

    /**
     * 缓存 普通对象
     *
     * @param key   键
     * @param value 值
     */
    public void setObject(String key, V value) {
        Assert.notNull(key, "key不允许为null");
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存 普通对象，并设置失效时间，指定默认时间单位为秒
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)。time要大于0，否则将设置为无期限
     */
    public void setObjectAndTime(String key, V value, long time) {
        setObjectAndTime(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 缓存 普通对象，并设置失效时间，不指定默认时间单位
     *
     * @param key   键
     * @param value 值
     * @param time  时间。time要大于0，否则将设置为无限期
     * @param unit  时间单位
     */
    public void setObjectAndTime(String key, V value, long time, TimeUnit unit) {
        Assert.notNull(key, "key不允许为null");
        Assert.notNull(unit, "unit不允许为null");
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, unit);
        } else {
            setObject(key, value);
        }
    }

    /**
     * 获取 普通缓存
     *
     * @param key 键
     * @return 值
     */
    public V getObject(String key) {
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }

    //==================================普通对象 相关操作   end========================================

    //==================================List 相关操作 begin========================================

    /**
     * 向List中缓存单个元素值
     *
     * @param key   键
     * @param value 值
     * @return 添加成功数量
     */
    public Long setListElement(String key, V value) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 向List中缓存单个元素值，并设置list的失效时间，默认时间单位为秒
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)，若小于等于0，则无期限
     * @return 添加成功数量
     */
    public Long setListElementAndTime(String key, V value, long time) {
        return setListElementAndTime(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 向List中缓存单个元素值，并设置list的失效时间，不指定时间单位
     *
     * @param key   键
     * @param value 值
     * @param time  时间，若小于等于0，则无期限
     * @param unit  时间单位
     * @return 添加成功数量
     */
    public Long setListElementAndTime(String key, V value, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        Long count = setListElement(key, value);
        if (time > 0) {
            Boolean result = expire(key, time, unit);
            if (null == result || !result) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
        return count;
    }

    /**
     * 向List中缓存多个元素值
     *
     * @param key   键
     * @param value 值
     * @return 添加成功数量
     */
    public Long setList(String key, List<V> value) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 向List中缓存多个元素值，并设置list的失效时间，默认时间单位为秒
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)，若小于等于0，则无期限
     * @return 添加成功数量
     */
    public Long setListAndTime(String key, List<V> value, long time) {
        return setListAndTime(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 向List中缓存多个元素值，并设置list的失效时间，不指定时间单位
     *
     * @param key  键
     * @param list list集合
     * @param time 时间，若小于等于0，则无期限
     * @param unit 时间单位
     * @return 添加成功数量
     */
    public Long setListAndTime(String key, List<V> list, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        Long count = setList(key, list);
        if (time > 0) {
            Boolean result = expire(key, time, unit);
            if (null == result || !result) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
        return count;
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引。index>=0时， 0表头，表示第一个元素，1 第二个元素，依次类推；index<0时，-1表尾，表示倒数第一个元素，-2倒数第二个元素，依次类推
     * @param value 值
     */
    public void updateListElement(String key, long index, V value) {
        Assert.notNull(key, "key不允许为null");
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 从list中删除N个值为value的元素
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public Long removeListElement(String key, long count, V value) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * 通过索引值获取list中的元素值
     *
     * @param key   键
     * @param index 索引。index>=0时， 0表头，表示第一个元素，1 第二个元素，依次类推；index<0时，-1表尾，表示倒数第一个元素，-2倒数第二个元素，依次类推
     * @return list中指定的元素值
     */
    public V getListElement(String key, long index) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 按照索引的起止值，获取 list缓存的内容(结果集中包含start和end)
     * 注意索引值：
     * 正数索引，下标0表示第一个，即：从索引0开始，要按照顺序（即 0、1、2、……、N-1）
     * 负数索引，下标-1表示倒数第一个，即：从索引倒数第N个开始，要按照顺序（即 -N、-(N-1)、-(N-2)、……、-1）
     * 特殊情况：start=0且end=-1，表示获取所有元素值
     * <p>
     * 注意索引值的逆序无效，即获取不到数据。假设总共有12个元素，情况如下：
     * start=-9且end=-10，无法获取到值，正确写法：start=-10且end=-9
     * start=10且end=9，无法获取到值，正确写法：start=9且end=10
     * start=-7且end=3，无法获取到值，正确写法：start=-9且end=5
     *
     * @param key   键
     * @param start 开始
     * @param end   结束
     * @return 符合要求的list对象
     */
    public List<V> getList(String key, long start, long end) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取list中的元素值（倒序返回）(结果集中包含start和end)
     *
     * @param key   键
     * @param start 开始
     * @param end   结束
     * @return 符合要求的list对象（倒序返回）
     */
    public List<V> getReverseList(String key, long start, long end) {
        List<V> list = getList(key, start, end);
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        Collections.reverse(list);
        return list;
    }

    /**
     * 获取list中所有元素值
     *
     * @param key 键
     * @return list的所有元素
     */
    public List<V> getList(String key) {
        return getList(key, 0, -1);
    }

    /**
     * 获取list中的所有元素值（倒序返回）
     *
     * @param key 键
     * @return list的所有元素（倒序返回）
     */
    public List<V> getReverseList(String key) {
        List<V> list = getList(key);
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        Collections.reverse(list);
        return list;
    }

    /**
     * 获取list元素的个数
     *
     * @param key 键
     * @return list元素的个数
     */
    public Long getListSize(String key) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForList().size(key);
    }

    //==================================List 相关操作   end========================================


    //==================================Set 相关操作 begin========================================

    /**
     * 将数据放入set缓存中
     *
     * @param key    键
     * @param values 值，可以是多个
     * @return 成功个数
     */
    public Long setSetElement(String key, V... values) {
        Assert.notNull(key, "key不允许为null");
        if (null == values || values.length == 0) {
            return 0L;
        }
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * 将set数据放入缓存，并设置set的失效时间，默认时间单位为秒
     *
     * @param key    键
     * @param time   时间(秒)，小于等于0，表示无期限
     * @param values 值，可以是多个
     * @return 成功个数
     */
    public Long setSetElementAndTime(String key, long time, V... values) {
        return setSetElementAndTime(key, time, TimeUnit.SECONDS, values);
    }

    /**
     * 将set数据放入缓存，并设置set的失效时间，不指定时间单位
     *
     * @param key    键
     * @param time   时间
     * @param unit   时间单位
     * @param values 值，可以是多个
     * @return 成功个数
     */
    public Long setSetElementAndTime(String key, long time, TimeUnit unit, V... values) {
        Assert.notNull(unit, "unit不允许为null");
        Long count = setSetElement(key, values);
        if (time > 0) {
            Boolean result = expire(key, time, unit);
            if (null == result || !result) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
        return count;
    }

    /**
     * 将数据放入set缓存中
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public Long setSet(String key, V[] values) {
        Assert.notNull(key, "key不允许为null");
        if (null == values || values.length == 0) {
            return 0L;
        }
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * 将set数据放入缓存，并设置set的失效时间，默认时间单位为秒
     *
     * @param key    键
     * @param time   时间(秒)，小于等于0，表示无期限
     * @param values 值
     * @return 成功个数
     */
    public Long setSetAndTime(String key, V[] values, long time) {
        return setSetAndTime(key, values, time, TimeUnit.SECONDS);
    }

    /**
     * 将set数据放入缓存，并设置set的失效时间，不指定时间单位
     *
     * @param key    键
     * @param values 值，可以是多个
     * @param time   时间
     * @param unit   时间单位
     * @return 成功个数
     */
    public Long setSetAndTime(String key, V[] values, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        Long count = setSet(key, values);
        if (time > 0) {
            Boolean result = expire(key, time, unit);
            if (null == result || !result) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
        return count;
    }

    /**
     * 从set中移除值为value的元素
     *
     * @param key    键
     * @param values 值，可以是多个
     * @return 移除的个数
     */
    public Long removeSetElement(String key, V... values) {
        Assert.notNull(key, "key不允许为null");
        if (null == values || values.length == 0) {
            return 0L;
        }
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 根据value从一个set中查询，是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public Boolean hasSetElement(String key, V value) {
        Assert.notNull(key, "key不允许为null");
        Boolean result = redisTemplate.opsForSet().isMember(key, value);
        return result != null && result;
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return set元素个数
     */
    public Long getSetSize(String key) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return set的所有元素值
     */
    public Set<V> getSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    //==================================Set 相关操作   end========================================


    //==================================HashMap 相关操作 begin========================================

    /**
     * 向指定key中缓存多个键值对
     *
     * @param key 键
     * @param map 对应多个键值
     */
    public void setHashMap(String key, Map<String, V> map) {
        Assert.notNull(key, "key不允许为null");
        if (CollectionUtils.isEmpty(map)) {
            return;
        }
        map.forEach((hashKey, value) -> {
            Assert.notNull(hashKey, "hashKey不允许为null");
        });
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 向指定key中缓存多个键值对并设置时间，默认时间单位为秒
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)，小于等于0表示无期限
     */
    public void setHashMapAndTime(String key, Map<String, V> map, long time) {
        setHashMapAndTime(key, map, time, TimeUnit.SECONDS);
    }

    /**
     * 向指定key中缓存多个键值对并设置时间，默认时间单位为秒
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)，小于等于0表示无期限
     * @param unit 时间单位
     */
    public void setHashMapAndTime(String key, Map<String, V> map, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        setHashMap(key, map);
        if (time > 0) {
            Boolean result = expire(key, time, unit);
            if (null == result || !result) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
    }

    /**
     * 向指定key中放入数据,如果不存在将创建
     * 注意：hashKey为null会报错
     *
     * @param key     键
     * @param hashKey 项
     * @param value   值
     */
    public void setHashMapElement(String key, String hashKey, V value) {
        Assert.notNull(key, "key不允许为null");
        Assert.notNull(hashKey, "hashKey不允许为null");
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * 向指定key中放入数据，并设置时间，默认时间单位为秒
     *
     * @param key     键
     * @param hashKey 项
     * @param value   值
     * @param time    时间(秒)，小于等于0表示无期限
     */
    public void setHashMapElementAndTime(String key, String hashKey, V value, long time) {
        setHashMapElementAndTime(key, hashKey, value, time, TimeUnit.SECONDS);
    }

    /**
     * 向指定key中放入数据，并设置时间
     *
     * @param key     键
     * @param hashKey 项
     * @param value   值
     * @param time    时间，小于等于0表示无期限
     * @param unit    时间单位
     */
    public void setHashMapElementAndTime(String key, String hashKey, V value, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        setHashMapElement(key, hashKey, value);
        if (time > 0) {
            Boolean result = expire(key, time, unit);
            if (null == result || !result) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
    }

    /**
     * 从hashMap中删除指定的项删除hash表中的值
     *
     * @param key      键
     * @param hashKeys 项，可以是多个
     * @return 成功删除个数
     */
    public Long removeHashMapElement(String key, String... hashKeys) {
        Assert.notNull(key, "key不允许为null");
        if (null == hashKeys || hashKeys.length == 0) {
            return 0L;
        }
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    /**
     * 判断hashMap中是否存在指定的项
     *
     * @param key     键
     * @param hashKey 项
     * @return true 存在 false不存在
     */
    public Boolean hasHashMapElement(String key, String hashKey) {
        Assert.notNull(hashKey, "hashKey不允许为null");
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 获取指定key的指定项的值
     *
     * @param key     键
     * @param hashKey 项
     * @return 值
     */
    public Object getHashMapElement(String key, String hashKey) {
        Assert.notNull(hashKey, "hashKey不允许为null");
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 获取指定key的所有键值
     *
     * @param key 键
     * @return hashMap的所有元素值
     */
    public Map<Object, Object> getHashMap(String key) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取hashMap中的元素个数
     *
     * @param key 键
     * @return hashMap的元素个数
     */
    public Long getHashMapSize(String key) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForHash().size(key);
    }

    //==================================HashMap 相关操作   end========================================


    //==================================ZSet 相关操作 begin========================================

    /**
     * 将数据放入zset缓存中(zset会按照score排序)
     *
     * @param key   键
     * @param value 值
     * @param score 排序分数
     * @return true：添加成功，false：添加失败
     */
    public Boolean setZSetElement(String key, V value, double score) {
        Assert.notNull(key, "key不允许为null");
        Boolean result = redisTemplate.opsForZSet().add(key, value, score);
        return result != null && result;
    }

    /**
     * 将数据放入zset缓存中(zset会按照score排序)，并设置整个ZSet的过期时间，默认时间单位为秒
     *
     * @param key   键
     * @param value 值
     * @param score 元素的分值
     * @param time  时间（秒），小于等于0表示无期限
     * @return true：添加成功；false：添加失败
     */
    public Boolean setZSetElementAndTime(String key, V value, double score, long time) {
        return setZSetElementAndTime(key, value, score, time, TimeUnit.SECONDS);
    }

    /**
     * 将数据放入zset缓存中(zset会按照score排序)，并设置整个ZSet的过期时间
     *
     * @param key   键
     * @param value 值
     * @param score 元素的分值
     * @param time  时间，小于等于0表示无期限
     * @param unit  时间单位
     * @return true：添加成功；false：添加失败
     */
    public Boolean setZSetElementAndTime(String key, V value, double score, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        Boolean result = setZSetElement(key, value, score);
        if (result != null && result) {
            if (time > 0) {
                // 设置整个zset的过期时间
                Boolean expireResult = expire(key, time, unit);
                if (null == expireResult || !expireResult) {
                    logger.debug("设置[{}]缓存的过期时间失败", key);
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 将数据放入zset缓存中(zset会按照存入数据时的时间戳排序)，并设置元素的过期时间，默认时间单位为秒
     * 实际上是通过移除指定分数来实现的 设置元素的过期时间
     * 注意：此种方式不能百分百做到 元素到期就能自动清除，原因是内部的清除逻辑是通过添加操作触发的。
     * 假设，最后一个元素和倒数第二个元素添加时间小于过期时间，如果在最后一个元素到期前，若一直没有触发添加操作，
     * 在此期间获取的元素中会包含最后一个元素和倒数第二个元素，甚至其他元素。
     * 拓展：若想获取元素的时候，不包含已经过期的元素，那么需要在获取时，也触发remove操作。
     *
     * @param key   键
     * @param value 值
     * @param time  时间（秒），小于等于0表示无期限
     * @return true：添加成功；false：添加失败
     */
    public Boolean setZSetElementAndElementTime(String key, V value, long time) {
        return setZSetElementAndElementTime(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 将数据放入zset缓存中(zset会按照存入数据时的时间戳排序)，并设置元素的过期时间
     * 实际上是通过移除指定分数来实现的 设置元素的过期时间
     * 注意：此种方式不能百分百做到 元素到期就能自动清除，原因是内部的清除逻辑是通过添加操作触发的。
     * 假设，最后一个元素和倒数第二个元素添加时间小于过期时间，如果在最后一个元素到期前，若一直没有触发添加操作，
     * 在此期间获取的元素中会包含最后一个元素和倒数第二个元素，甚至其他元素。
     * 拓展：若想获取元素的时候，不包含已经过期的元素，那么需要在获取时，也触发remove操作。
     *
     * @param key   键
     * @param value 值
     * @param time  时间，小于等于0表示无期限
     * @param unit  时间单位
     * @return true：添加成功；false：添加失败
     */
    public Boolean setZSetElementAndElementTime(String key, V value, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        long currentTime = System.currentTimeMillis();
        Boolean result = setZSetElement(key, value, currentTime);
        long millisTime = unit.toMillis(time);
        if (result != null && result) {
            if (time > 0) {
                // 移除过期元素
                removeZSetElement(key, 0, currentTime - millisTime);
                // 若一直未触发添加操作，会导致最后增加的元素无法过期。因此，此处还需要 设置整个zset的过期时间
                Boolean expireResult = expire(key, time, unit);
                if (null == expireResult || !expireResult) {
                    logger.debug("设置[{}]缓存的过期时间失败", key);
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 将数据放入zset缓存中(zset会按照score排序)
     *
     * @param key 键
     * @param set 带有分数的set集合
     * @return 添加成功数量
     */
    public Long setZSet(String key, Set<ZSetOperations.TypedTuple<V>> set) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().add(key, set);
    }

    /**
     * 将数据放入zset缓存中(zset会按照score排序)，并设置整个zset的过期时间，默认时间单位为秒
     *
     * @param key  键
     * @param set  带有分数的set集合
     * @param time 时间，小于等于0表示无期限
     * @return 添加成功数量
     */
    public Long setZSetAndTime(String key, Set<ZSetOperations.TypedTuple<V>> set, long time) {
        return setZSetAndTime(key, set, time, TimeUnit.SECONDS);
    }

    /**
     * 将数据放入zset缓存中(zset会按照score排序)，并设置整个zset的过期时间
     *
     * @param key  键
     * @param set  带有分数的set集合
     * @param time 时间，小于等于0表示无期限
     * @param unit 时间单位
     * @return 添加成功数量
     */
    public Long setZSetAndTime(String key, Set<ZSetOperations.TypedTuple<V>> set, long time, TimeUnit unit) {
        Assert.notNull(unit, "unit不允许为null");
        Long count = setZSet(key, set);
        if (time > 0) {
            Boolean expireResult = expire(key, time, unit);
            if (null == expireResult || !expireResult) {
                logger.debug("设置[{}]缓存的过期时间失败", key);
            }
        }
        return count;
    }

    /**
     * 删除zset中指定分数间的元素（> minScore 且 <= maxScore）
     *
     * @param key      键
     * @param minScore 小分数
     * @param maxScore 大分数
     * @return 成功删除个数
     */
    public Long removeZSetElement(String key, double minScore, double maxScore) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
    }

    /**
     * 删除指定的元素值
     *
     * @param key    键
     * @param values 元素值
     * @return 成功删除的个数
     */
    public Long removeZSetElement(String key, V[] values) {
        Assert.notNull(key, "key不允许为null");
        if (null == values || values.length == 0) {
            return 0L;
        }
        return redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 获取ZSet中指定元素
     *
     * @param key   键
     * @param index 索引值。index>=0时， 0表示第一个元素，1 第二个元素，依次类推；index<0时，-1表示倒数第一个元素，-2倒数第二个元素，依次类推
     * @return 元素值
     */
    public V getZSetElementByIndex(String key, long index) {
        Set<V> set = getZSetElementByIndex(key, index, index);
        if (CollectionUtils.isEmpty(set)) {
            return null;
        }
        for (V value : set) {
            return value;
        }
        return null;
    }

    /**
     * 根据索引获取ZSet中的元素值（结果集中包括start和end）
     * 注意索引值：
     * 正数索引，下标0表示第一个，即：从索引0开始，要按照顺序（即 0、1、2、……、N-1）
     * 负数索引，下标-1表示倒数第一个，即：从索引倒数第N个开始，要按照顺序（即 -N、-(N-1)、-(N-2)、……、-1）
     * 特殊情况：start=0且end=-1，表示获取所有元素值
     * <p>
     * 注意索引值的逆序无效，即获取不到数据。假设总共有12个元素，情况如下：
     * start=-9且end=-10，无法获取到值，正确写法：start=-10且end=-9
     * start=10且end=9，无法获取到值，正确写法：start=9且end=10
     * start=-7且end=3，无法获取到值，正确写法：start=-9且end=5
     *
     * @param key   键
     * @param start 索引开始值
     * @param end   索引结束值
     * @return 满足条件的ZSet中的元素值
     */
    public Set<V> getZSetElementByIndex(String key, long start, long end) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 根据索引获取ZSet中的元素值（倒序）（结果集中包括start和end）
     * 注意：是先对zset进行倒序排列之后，再根据下标取值
     *
     * @param key   键
     * @param start 索引开始值
     * @param end   索引结束值
     * @return 满足条件的ZSet中的元素值
     */
    public Set<V> getReverseZSetElementByIndex(String key, long start, long end) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 根据score分数获取ZSet中的元素值
     * >min 且 <= max
     *
     * @param key 键
     * @param min 分数开始值
     * @param max 分数结束值
     * @return 满足条件的ZSet中的元素值
     */
    public Set<V> getZSetElementByScore(String key, double min, double max) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 根据score分数获取ZSet中的元素值（倒序）
     * 注意：是先对zset进行倒序排列之后，再根据score取值
     * >min 且 <= max
     *
     * @param key 键
     * @param min 分数开始值
     * @param max 分数结束值
     * @return 满足条件的ZSet中的元素值
     */
    public Set<V> getReverseZSetElementByScore(String key, double min, double max) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 获取ZSet中的所有元素值
     *
     * @param key 键
     * @return ZSet中的所有元素值
     */
    public Set<V> getZSet(String key) {
        return getZSetElementByIndex(key, 0, -1);
    }

    /**
     * 获取ZSet中的所有元素值（倒序）
     *
     * @param key 键
     * @return ZSet中的所有元素值
     */
    public Set<V> getReverseZSet(String key) {
        return getReverseZSetElementByIndex(key, 0, -1);
    }

    /**
     * 获取ZSet中的元素个数
     *
     * @param key 键
     * @return ZSet的元素个数
     */
    public Long getZSetSize(String key) {
        Assert.notNull(key, "key不允许为null");
        return redisTemplate.opsForZSet().size(key);
    }

    //==================================ZSet 相关操作   end========================================


    public void setRedisTemplate(RedisTemplate<String, V> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
